﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;

namespace Comp376A2_9609695
{
    /// <summary>
    /// General monster class
    /// </summary>
    public abstract class Monster: Updateable, Solid
    {
        private int id;
        private static int count = 0;
        protected bool dead = false;
        public bool isDead { get { return dead; } }        
        protected int elapsed = 0;
        protected bool ghosting = false;
        protected bool dying = false;
        protected bool squished = false;
        public bool isSquished { get { return squished; } }
        private int deathTimer = 0;
        protected bool chase = true;
        protected int ghostTimer = 0;
        protected int ghostTimeout;
        private Random r = new Random();
        protected int pumpTimer;
        protected Vector2 ghostTarget;
        protected Vector2 ghostStart;
        protected int speed;
        public int Speed
        {
            get {return speed;}
            set
            {
                if (value < 0)
                    value = -value;
                speed = value;
            }
        }
        public Vector2 position;
        protected Vector2 startPos;
        protected int pumpLevel = 0;
        public enum Direction { Left, Up, Right, Down };
        public Direction facing;

        protected Cue soundCue;
        protected SoundBank bank;
        public SoundBank soundbank
        {
            get { return bank; }
            set { bank = value; soundCue = soundbank.GetCue("pump"); }
        }
        

        public Monster()
        {
            position = new Vector2(0, 0);
            startPos = new Vector2(0, 0);
            ghostTarget = new Vector2(0, 0);
            speed = 9;
            facing = Direction.Left;
            id = count++;
        }

        /// <summary>
        /// Start the monster at specific coordinates
        /// </summary>
        /// <param name="startX">X position</param>
        /// <param name="startY">Y position</param>
        public Monster(int startX, int startY)
        {
            position = new Vector2(startX, startY);
            startPos = new Vector2(startX, startY);
            speed = 8;
            ghostTimeout = r.Next(3000) + 4000;
            id = count++;
        }

        /// <summary>
        /// Reset the monster. Move it back to the start, stop it from ghosting.
        /// </summary>
        public virtual void reset()
        {
            ghostTimer = 0;
            elapsed = 0;
            position = startPos;
            ghosting = false;
            ghostTimeout = r.Next(3000) + 4000;
            pumpLevel = 0;
        }

        /// <summary>
        /// Move the monster. Monsters can only travel through existing tunnels
        /// </summary>
        /// <param name="control"></param>
        public void move(GameController control)
        {
            Level level = control.level;
            DigDug player = control.player;
            Vector2 adjPos = new Vector2((int)(position.X - level.X), (int)(position.Y - level.Y));
            Vector2 levPos = level.getGridPos((int)(position.X - level.X), (int)(position.Y - level.Y));
            List<Direction> canMoveTo = new List<Direction>();
            // If the monster is fully in a cell, let it change directions.
            if ((int)adjPos.X % 16 == 0 && (int)adjPos.Y % 16 == 0)
            {
                byte currCell = level.getCell((int)levPos.X, (int)levPos.Y);
                byte nextCell;

                // Monsters can not turn around
                nextCell = level.getCell((int)levPos.X + 1, (int)levPos.Y);
                if ((nextCell & 1) != 0 && (currCell & 4) != 0
                    && facing != Direction.Left && levPos.X < 176)
                    canMoveTo.Add(Direction.Right);

                nextCell = level.getCell((int)levPos.X, (int)levPos.Y + 1);
                if ((nextCell & 2) != 0 && (currCell & 8) != 0
                    && facing != Direction.Up && levPos.Y < 192)
                    canMoveTo.Add(Direction.Down);

                nextCell = level.getCell((int)levPos.X - 1, (int)levPos.Y);
                if ((nextCell & 4) != 0 && (currCell & 1) != 0
                    && facing != Direction.Right
                    && levPos.X > 0)
                    canMoveTo.Add(Direction.Left);

                nextCell = level.getCell((int)levPos.X, (int)levPos.Y - 1);
                if ((nextCell & 8) != 0 && (nextCell & 2) != 0
                    && facing != Direction.Down && levPos.Y > 0)
                    canMoveTo.Add(Direction.Up);

                Random r = new Random();
                Direction moveTo;
                if (canMoveTo.Count > 0)
                {
                    // Move in a random, valid direction
                    moveTo = canMoveTo.ElementAt(r.Next(canMoveTo.Count));
                    // If the monster is in chase mode, move toward the player instead
                    if (chase)
                    {
                        bool vertical = false;
                        if (Math.Abs(player.position.X - position.X) < Math.Abs(player.position.Y - position.Y))
                            vertical = true;
                        if (vertical)
                        {
                            if (canMoveTo.Contains(Direction.Up) && player.position.Y < position.Y && facing != Direction.Down)
                                moveTo = Direction.Up;
                            if (canMoveTo.Contains(Direction.Down) && player.position.Y > position.Y && facing != Direction.Up)
                                moveTo = Direction.Down;
                        }
                        else
                        {
                            if (canMoveTo.Contains(Direction.Left) && player.position.X < position.X && facing != Direction.Right)
                                moveTo = Direction.Left;
                            if (canMoveTo.Contains(Direction.Right) && player.position.X > position.X && facing != Direction.Left)
                                moveTo = Direction.Right;
                        }
                    }
                }
                else
                {
                    switch (facing)
                    {
                        case Direction.Left:
                            moveTo = Direction.Right;
                            break;
                        case Direction.Right:
                            moveTo = Direction.Left;
                            break;
                        case Direction.Up:
                            moveTo = Direction.Down;
                            break;
                        case Direction.Down:
                            moveTo = Direction.Up;
                            break;
                        default:
                            moveTo = facing;
                            break;
                    }
                }
                facing = moveTo;
            }
        }

        public virtual void update(GameTime time, GameController control)
        {
            elapsed = time.ElapsedGameTime.Milliseconds;
            Level level = control.level;
            DigDug player = control.player;

            // If the monster hits a rock, it is squished
            Rock.State rockState = Rock.State.Static;
            if (control.collidesRock(new Rectangle((int)(position.X), (int)(position.Y), 16, 16), ref rockState))
            {
                if (rockState == Rock.State.Falling)
                {
                    squished = true;
                }
            }

            // A short delay before disappearing from the screen on death
            if (dying)
            {
                deathTimer += elapsed;
                if (deathTimer >= 500)
                    dead = true;
            }
            // If a monster is squished, drop it like it's hot
            else if (squished)
            {
                byte cellBelow = level.getCellAt((int)position.X - level.X, (int)position.Y - level.Y + 16);
                if ((cellBelow != 0 || (int)(position.Y - level.Y) % 16 != 0) && (position.Y - level.Y) < 192)
                {
                    position.Y += 16 / elapsed;
                    cellBelow |= 10;
                }
                else
                {
                    dying = true;
                    deathTimer = 0;
                }
            }
            else
            {
                ghostTimer += elapsed;
                Vector2 adjPos = new Vector2((int)(position.X - level.X), (int)(position.Y - level.Y));
                Vector2 levPos = level.getGridPos((int)(position.X - level.X), (int)(position.Y - level.Y));
                Vector2 velocity = new Vector2(0, 0);
                // If the player is within 5 squares, try to chase them
                if (Vector2.Distance(player.position, position) < 80)
                    chase = true;
                else chase = false;
                // If the monster collides with the player's pump, start being pumped
                if (player.state == DigDug.State.Throwing && collides(player.pumpBox, true))
                {
                    pumpTimer = 0;
                    player.state = DigDug.State.Pumping;
                    player.attach(this);
                    player.pump();
                }
                // If the monster is being pumped, slowly deflate unless it pops
                if (pumpLevel >= 1)
                {
                    pumpTimer += elapsed;
                    if (pumpTimer >= 500)
                    {
                        pumpLevel--;
                        pumpTimer = 0;
                    }
                    if (pumpLevel == 0)
                    {
                        player.detach(this);
                    }
                    if (pumpLevel >= 6)
                    {
                        dying = true;
                        player.detach(this);
                        soundCue = bank.GetCue("monsterdie");
                        soundCue.Play();
                    }
                }
                // If enough time has passed, the monster is not chasing or being pumped and it's fully in a cell go into ghost mode
                if (ghostTimer >= ghostTimeout && !ghosting && !chase && adjPos.X % 16 == 0 && adjPos.Y % 16 == 0 && pumpLevel == 0)
                {
                    ghosting = true;
                    ghostTimer = 0;
                    ghostStart = new Vector2(levPos.X, levPos.Y);
                    ghostTarget = level.getGridPos((int)player.position.X - level.X, (int)player.position.Y - level.Y);
                }
                // Move in the direction the monster is facing
                if (!ghosting && pumpLevel == 0)
                {
                    move(control);
                    if (facing == Direction.Left)
                        velocity.X = -1;
                    if (facing == Direction.Up)
                        velocity.Y = -1;
                    if (facing == Direction.Right)
                        velocity.X = 1;
                    if (facing == Direction.Down)
                        velocity.Y = 1;
                }
                // In ghost mode, move freely
                else
                {
                    // When a tunnel or the target is reached, exit ghost mode
                    if (levPos.X == ghostTarget.X && levPos.Y == ghostTarget.Y
                        && adjPos.X % 16 == 0 && adjPos.Y % 16 == 0)
                    {
                        ghosting = false;
                        ghostTimer = 0;
                        ghostTimeout = r.Next(3000) + 4000;
                    }
                    if (adjPos.X % 16 == 0 && adjPos.Y % 16 == 0
                        && levPos.X != ghostStart.X && levPos.Y != ghostStart.Y)
                    {
                        byte cell = level.getCellAt((int)adjPos.X, (int)adjPos.Y);
                        if (cell != 0)
                        {
                            ghosting = false;
                            ghostTimer = 0;
                        }
                    }
                    if (ghosting && pumpLevel == 0)
                    {
                        if (adjPos.X < 16 * ghostTarget.X)
                            velocity.X = 1;
                        if (adjPos.X > 16 * ghostTarget.X)
                            velocity.X = -1;
                        if (adjPos.Y < 16 * ghostTarget.Y)
                            velocity.Y = 1;
                        if (adjPos.Y > 16 * ghostTarget.Y)
                            velocity.Y = -1;

                        if (velocity.X != 0 || velocity.Y != 0)
                            velocity.Normalize(); // Move uniformly. Diagonals are not faster
                    }
                }
                if (elapsed > 0)
                {
                    position.X += velocity.X * ((speed / (ghosting ? 1.5f : 1)) / elapsed);
                    position.Y += velocity.Y * ((speed / (ghosting ? 1.5f : 1)) / elapsed);
                }

                // If the monster collides with the player, lose a life
                if (collides(new Rectangle((int)player.position.X + 1, (int)player.position.Y + 1, 14, 14)))
                {
                    control.state = GameController.GameState.LoseLife;
                    player.state = DigDug.State.Dead;
                }
            }
        }

        /// <summary>
        /// Pump up the monster
        /// </summary>
        public void pump()
        {
            if (soundCue.IsPrepared && soundCue.Name == "pump")
                soundCue.Play();
            else
                soundCue = bank.GetCue("pump");
            pumpLevel++;
            pumpTimer = 0;
        }

        /// <summary>
        /// Return whether the monster collides with a box
        /// </summary>
        /// <param name="box">The bounding box to check</param>
        /// <returns></returns>
        public virtual bool collides(Rectangle box)
        {
            return collides(box, false);
        }

        /// <summary>
        /// Return whether the monster collides with a box, taking into account if DigDug is pumping
        /// </summary>
        /// <param name="box">The bounding box to check</param>
        /// <param name="pumping">Whether or not DigDug is pumping</param>
        /// <returns></returns>
        public virtual bool collides(Rectangle box, bool pumping = false)
        {
            if ((ghosting || pumpLevel != 0) && !pumping)
                return false;
            else
            {
                Rectangle bounds = new Rectangle((int)position.X+1, (int)position.Y+1, 14, 14);
                Rectangle intersection = new Rectangle();
                Rectangle.Intersect(ref box, ref bounds, out intersection);
                return (intersection.Width != 0 && intersection.Height != 0);
            }
        }

        /// <summary>
        /// Equality is determined by whether or not two monsters have the same ID
        /// </summary>
        /// <param name="obj">The object to check against</param>
        /// <returns>True if they are the same</returns>
        public override bool Equals(Object obj)
        {
            if (obj is Monster)
            {
                Monster m = (Monster)obj;
                if (m.id == id)
                    return true;
                return false;
            }
            else
                return base.Equals(obj);
        }
    }
}
